#ifndef SBTVECTOR2_HPP_INCLUDED
#define SBTVECTOR2_HPP_INCLUDED

#include "SbtPrerequisites.h"
#include "SbtMath.h"

#include <cassert>
#include <iostream>

namespace Sbt
{

class SBT_EXPORT Vector2
{
public:
    float x,y;

    Vector2()
        : x(0), y(0)
    {}

    Vector2(const float x_, const float y_)
        : x(x_), y(y_)
    {}

    Vector2(const Vector2& other)
        : x(other.x), y(other.y)
    {
    }

    explicit Vector2(const float scalar)
        : x(scalar), y(scalar)
    {
    }

    explicit Vector2(const float array[2])
        : x(array[0]), y(array[1])
    {
    }

    explicit Vector2(const int array[2])
    {
        x = static_cast<float>(array[0]);
        y = static_cast<float>(array[1]);
    }

    explicit Vector2(float* const ptr)
        : x(ptr[0]), y(ptr[1])
    {
    }

    Vector2& operator=(const Vector2& rhs)
    {
        x = rhs.x; y = rhs.y;
        return *this;
    }

    float* ptr()
    {
        return &x;
    }

    const float* ptr() const
    {
        return &x;
    }

    bool operator==(const Vector2& rhs) const
    {
        return ( x == rhs.x && y == rhs.y );
    }

    bool operator!=(const Vector2& rhs) const
    {
        return !operator==(rhs);
    }

    bool operator>(const Vector2& rhs) const
    {
        return ( x>rhs.x && y>rhs.y );
    }

    bool operator<=(const Vector2& rhs) const
    {
        return !operator>(rhs);
    }

    bool operator<(const Vector2& rhs) const
    {
        return ( x<rhs.x && y<rhs.y );
    }

    bool operator>=(const Vector2& rhs) const
    {
        return !operator<(rhs);
    }

    const float operator[](const size_t index) const
    {
        assert( index < 2 );
        return *(&x + index);
    }

    float& operator[](const size_t index)
    {
        assert( index < 2 );
        return *(&x + index);
    }

    const Vector2& operator+() const
    {
        return *this;
    }

    const Vector2 operator-() const
    {
        return Vector2(-x,-y);
    }

    Vector2& operator+=(const Vector2& rhs)
    {
        x += rhs.x; y += rhs.y;
        return *this;
    }

    Vector2& operator+=(const float scalar)
    {
        x += scalar; y += scalar;
        return *this;
    }

    Vector2& operator-=(const Vector2& rhs)
    {
        x -= rhs.x; y -= rhs.y;
        return *this;
    }

    Vector2& operator-=(const float scalar)
    {
        x -= scalar; y -= scalar;
        return *this;
    }

    Vector2& operator*=(const Vector2& rhs)
    {
        x *= rhs.x; y *= rhs.y;
        return *this;
    }

    Vector2& operator*=(const float scalar)
    {
        x *= scalar; y *= scalar;
        return *this;
    }

    Vector2& operator/=(const Vector2& rhs)
    {
        assert( rhs.x != 0.0f && rhs.y != 0.0f );
        x /= rhs.x; y /= rhs.y;
        return *this;
    }

    Vector2& operator/=(const float scalar)
    {
        assert( scalar != 0.0f );
        x /= scalar; y /= scalar;
        return *this;
    }

    friend Vector2 operator+(const Vector2& lhs, const Vector2& rhs)
    {
        Vector2 sum(lhs);
        sum += rhs;
        return sum;
    }

    friend const Vector2 operator+(const Vector2& lhs, const float scalar)
    {
        Vector2 sum(lhs);
        sum += scalar;
        return sum;
    }

    friend const Vector2 operator+(const float scalar, const Vector2& rhs)
    {
        Vector2 sum(scalar);
        sum += rhs;
        return sum;
    }

    friend const Vector2 operator-(const Vector2& lhs, const Vector2& rhs)
    {
        Vector2 diff(lhs);
        diff -= rhs;
        return diff;
    }

    friend const Vector2 operator-(const Vector2& lhs, const float scalar)
    {
        Vector2 diff(lhs);
        diff -= scalar;
        return diff;
    }

    friend const Vector2 operator-(const float scalar, const Vector2& rhs)
    {
        Vector2 diff(scalar);
        diff -= rhs;
        return diff;
    }

    friend const Vector2 operator*(const Vector2& lhs, const Vector2& rhs)
    {
        Vector2 prod(lhs);
        prod *= rhs;
        return prod;
    }

    friend const Vector2 operator*(const Vector2& lhs, const float scalar)
    {
        Vector2 prod(lhs);
        prod *= scalar;
        return prod;
    }

    friend const Vector2 operator*(const float scalar, const Vector2& rhs)
    {
        Vector2 prod(scalar);
        prod *= rhs;
        return prod;
    }

    friend const Vector2 operator/(const Vector2& lhs, const Vector2& rhs)
    {
        assert( rhs.x !=0.0f && rhs.y != 0.0f );
        Vector2 quotient(lhs);
        quotient /= rhs;
        return quotient;
    }

    friend const Vector2 operator/(const Vector2& lhs, const float scalar)
    {
        assert( scalar !=0.0f );
        Vector2 quotient(lhs);
        quotient /= scalar;
        return quotient;
    }

    friend const Vector2 operator/(const float scalar, const Vector2& rhs)
    {
        assert( rhs.x !=0.0f && rhs.y != 0.0f );
        Vector2 quotient(scalar);
        quotient /= rhs;
        return quotient;
    }

    float squaredLength() const
    {
        return x*x + y*y;
    }

    float length() const
    {
        return Math::sqrt( squaredLength() );
    }

    float normalise()
    {
        float len = length();
        if ( !Math::floatEquals(len, 0.0f, 1e-8f) )
        {
            x /= len;
            y /= len;
        }
        return len;
    }

    float dotProduct(const Vector2& other) const
    {
        return x*other.x + y*other.y;
    }

    float distance(const Vector2& other) const
    {
        return (*this - other).length();
    }

    float squaredDistance(const Vector2& other) const
    {
        return (*this - other).squaredLength();
    }

    Vector2 midPoint(const Vector2& other) const
    {
        return Vector2( (x+other.x)/2 , (y+other.y)/2 );
    }

    friend std::ostream& operator<<(std::ostream& ostrm, const Vector2& rhs)
    {
        ostrm<<"Vector2( "<<rhs.x<<" , "<<rhs.y<<" )";
        return ostrm;
    }

    static const Vector2 Zero;
    static const Vector2 Unit_X;
    static const Vector2 Unit_Y;
    static const Vector2 Negative_Unit_X;
    static const Vector2 Negative_Unit_Y;
    static const Vector2 Unit;
};

inline const Vector2 rotate(const Vector2& src, const float rad) {
    Vector2 ret;
    ret.x = src.x * Math::cos(rad) - src.y * Math::sin(rad);
    ret.y = src.x * Math::sin(rad) + src.y * Math::cos(rad);
    return ret;
}

} // namespace Sbt

#endif // SBTVECTOR2_HPP_INCLUDED
